Generics עזאם מרעי המחלקה למדעי המחשב אוניברסיטת בן-גוריון
2 Example List myintlist = new LinkedList(); // 1 myintlist.add(new Integer(0)); // 2 Integer x = myintlist.iterator().next(); // 3
3 Example List myintlist = new LinkedList(); // 1 myintlist.add(new Integer(0)); // 2 Integer x = (Integer) myintlist.iterator().next(); // 3
4 Example List myintlist = new LinkedList(); // 1 myintlist.add(new Integer(0)); // 2 Integer x = (Integer) myintlist.iterator().next(); // 3 The cast on line 3 is annoying. Typically, the programmer knows what kind of data has been placed into a particular list. However, the cast is essential. The compiler can only guarantee that an Object will be returned by the iterator. To ensure the assignment to a variable of type Integer is type safe, the cast is required. Of course, the cast not only introduces clutter. It also introduces the possibility of a run time error. ClassCastException: java.lang.double cannot be cast to java.lang.integer LinkedList myintlist = new LinkedList(); myintlist.add(new Double(0)); Integer x = (Integer) myintlist.iterator().next();
5 Example List myintlist = new LinkedList(); // 1 myintlist.add(new Integer(0)); // 2 Integer x = (Integer) myintlist.iterator().next(); // 3 The cast on line 3 is annoying. Typically, the programmer knows what kind of data has been placed into a particular list. However, the cast is essential. The compiler can only guarantee that an Object will be returned by the iterator. To ensure the assignment to a variable of type Integer is type safe, the cast is required. Of course, the cast not only introduces clutter. It also introduces the possibility of a run time error. What if programmers could actually express their intent, and mark a list as being restricted to contain a particular data type? This is the core idea behind generics.
6 Solution: Generic Classes Generic (Parametric) classes are classes that have Type Parameters A generic class with a concrete type parameter is an instance of the generic class The type parameter can be the type of variables, parameters or return values.
7 Generic Classes in Java Declaring a generic interface or class: interface List<T> { public void add(t t) {...... public class LinkedList<T> implements List<T> { private T[] _data;...
8 Example List myintlist = new LinkedList(); // 1 myintlist.add(new Integer(0)); // 2 Integer x = (Integer) myintlist.iterator().next(); // 3 Solution: USE GENERIC List<Integer> myintlist = new LinkedList<Integer>(); myintlist.add(new Integer(0)); Integer x = myintlist.iterator().next();
9 Example List<Integer> myintlist = new LinkedList<Integer>(); // 1 myintlist.add(new Integer(0)); //2 Integer x = myintlist.iterator().next(); // 3 Notice the type declaration for the variable myintlist. It specifies that this is not just an arbitrary List, but a List of Integer, written List<Integer>. We say that List is a generic interface that takes a type parameter - in this case, Integer. We also specify a type parameter when creating the list object. The other thing to pay attention to is that the cast is gone on line 3. Now, you might think that all we ve accomplished is to move the clutter around However, there is a very big difference here. The compiler can now check the type correctness of the program at compile-time. When we say that myintlist is declared with type List<Integer>, this tells us something about the variable myintlist, which holds true wherever and whenever it is used, and the compiler will guarantee it. In contrast, the cast tells us something the programmer thinks is true at a single point in the code.
10 Generic Classes in Java (cont d) Creating instances of generics: List<Integer> names = new LinkedList<Integer>(); Generic and Concrete types Generic Type: The parametrized type, e.g List Concrete Type: A generic type with a concrete parameter, e.g List<Integer>
11 Defining Simple Generics interface List<T> { public void add(t t) {...... public class LinkedList<T> implements List<T> { private T[] _data;... This should all be familiar, except for the stuff in angle brackets Type parameters can be used throughout the generic declaration, pretty much where you would use ordinary types
12 Defining Simple Generics interface List<T> { public void add(t t) {...... public class LinkedList<T> implements List<T> { private T[] _data;... In the introduction, we saw invocations of the generic type declaration List, such as List<Integer>. In the invocation (usually called a parameterized type), all occurrences of the formal type parameter (E in this case) are replaced by the actual type You might imagine that List<Integer> stands for a version of List where E has been uniformly replaced by Integer: public interface IntegerList { void add(integer x);
13 A helpful, but misleading intuition You might imagine that List<Integer> stands for a version of List where E has been uniformly replaced by Integer It is helpful, because the parameterized type List<Integer> does indeed have methods that look just like this expansion It is misleading, because the declaration of a generic is never actually expanded in this way There aren t multiple copies of the code: not in source, not in binary, not on disk and not in memory This is very different than a C++ template A generic type declaration is compiled once and for all, and turned into a single class file, just like an ordinary class or interface declaration Type parameters are analogous to the ordinary parameters used in methods or constructors Much like a method has formal value parameters that describe the kinds of values it operates on, a generic declaration has formal type parameters When a generic declaration is invoked, the actual type arguments are substituted for the formal type parameters
14 Reminder: Sub-Types and Substitution Sub-Typing defines the class relation B is a subtype of A, marked B A. According to the substitution principle, if B A, then an instance of B can substitute an instance of A. Therefore, it is legal to assign an instance of B b to a reference of A a: a b
15 Covariant Arrays in Java Covariant: a Cat[] is a Animal[] Early versions of Java and C# did not include generics Making arrays invariant rules out useful polymorphic programs boolean equalarrays (Object[] a1, Object[] a2); void shufflearray(object[] a); Covariant arrays leads to problem with writes into the array
16 Covariant Arrays in Java (Cont ) Example: Animal[] an; Cat[] ct = new Cat[30]; an = ct; an[0]= new Animal(); Cat cat = (Cat) an[0]; Runt time error: ArrayStoreException Reading from ct is safely. It is only trying to write to the array that can lead to trouble. Performance problem: each write into an array requires an additional runtime check.
17 Example 2 String[] a = new String[1]; Object[] b = a; b[0] = new A(); Runt time error: ArrayStoreException
18 Generics and Subtyping Let s test our understanding of generics. Is the following code snippet legal? List<String> ls = new ArrayList<String>(); //1 List<Object> lo = ls; //2
19 Generics and Subtyping Let s test our understanding of generics. Is the following code snippet legal? List<String> ls = new ArrayList<String>(); //1 List<Object> lo = ls; //2 Line 1 is certainly legal. The trickier part of the question is line 2. This boils down to the question: is a List of String a List of Object?? Most people s instinct is to answer: sure! Well, take a look at the next few lines: lo.add(new Object()); // 3 String s = ls.get(0); // 4: attempts to assign an Object to a String! The Java compiler will prevent this from happening: line 2 will cause a compile time error
20 G<Child> is not a subtype of G<Parent> If Foo is a subclass of Bar, and G is some generic type declaration, it is not the case that G<Foo> is a subtype of G<Bar>
21 Wildcards Consider writing a routine that prints out all the elements in a collection: Not using generics void printcollection(collection c) { Iterator i = c.iterator(); for (k = 0; k < c.size(); k++) { System.out.println(i.next()); And here is a naive attempt at writing it using generics: Using generics void printcollection(collection<object> c) { for (Object e : c) { System.out.println(e); The problem is that this new version is much less useful than the old one. Whereas the old code could be called with any kind of collection as a parameter, the new code only takes Collection<Object>, which, as we ve just demonstrated, is not a supertype of all kinds of collections!
22 Wildcards? So what is the supertype of all kinds of collections? It s written Collection<?>, that is, a collection whose element type matches anything void printcollection(collection<?> c) { for (Object e : c) { System.out.println(e);? and now, we can call it with any type of collection Notice that inside printcollection(), we can still read elements from c and give them type Object This is always safe, since whatever the actual type of the collection, it does contain objects. Collection<?> c = new ArrayList<String>(); c.add(new Object());
23 Wildcards? So what is the supertype of all kinds of collections? It s written Collection<?>, that is, a collection whose element type matches anything void printcollection(collection<?> c) { for (Object e : c) { System.out.println(e);? and now, we can call it with any type of collection Notice that inside printcollection(), we can still read elements from c and give them type Object This is always safe, since whatever the actual type of the collection, it does contain objects. Collection<?> c = new ArrayList<String>(); c.add(new c.add("a"); Object());
24 Wildcards? So what is the supertype of all kinds of collections? It s written Collection<?>, that is, a collection whose element type matches anything void printcollection(collection<?> c) { for (Object e : c) { System.out.println(e);? and now, we can call it with any type of collection Notice that inside printcollection(), we can still read elements from c and give them type Object This is always safe, since whatever the actual type of the collection, it does contain objects. It isn t safe to add arbitrary objects to it however: Collection<?> c = new ArrayList<String>(); c.add(new Object()); // compile time error
25 Passing data to a wildcard? When the actual type parameter is?, it stands for some unknown type Since we don t know what type that is, we cannot pass anything in The sole exception is null, which is a member of every type On the other hand, given a List<?>, we can call get() and make use of the result The result type is an unknown type, but we always know that it is an object It is therefore safe to assign the result of get() to a variable of type Object or pass it as a parameter where the type Object is expected
26 Bounded Wildcards Consider a simple drawing application that can draw shapes such as rectangles and circles: abstract class Shape { abstract void draw(canvas c); class Circle extends Shape { private int x, y, radius; void draw(canvas c) {... class Rectangle extends Shape { private int x, y, width, height; public void draw(canvas c) {... it would be convenient to have a method in Canvas that draws a list of shapes: public void drawall(list<shape> shapes) { for (Shape s : shapes) { s.draw(this); The type rules say that drawall() can only be called on lists of exactly Shape
27 Bounded Wildcards What we really want is for the method to accept a list of any kind of shape: public void drawall(list<? extends Shape> shapes) {... There is a small but significant difference here: we have replaced the type List<Shape> with List<? extends Shape> Now drawall() will accept lists of any subclass of Shape, so we can now call it on a List<Circle> if we want List<? extends Shape> is an example of a bounded wildcard. The? Stands for an unknown type, just like the wildcards we saw earlier However, in this case, we know that this unknown type is in fact a subtype of Shape. We say that Shape is the upper bound of the wildcard
28 Price of using <? extends Shape> There is, as usual, a price to be paid for the flexibility of using wildcards That price is that it is now illegal to write into shapes in the body of the method For instance, this is not allowed: public void addrectangle(list<? extends Shape> shapes) { shapes.add(0, new Rectangle()); // compile-time error! You should be able to figure out why the code above is disallowed
29 Generic Methods Consider writing a method that takes an array of objects and a collection and puts all objects in the array into the collection Here is a first attempt: static void fromarraytocollection(object[] a, Collection<?> c) { for (Object o : a) { c.add(o); // compile time error By now, you will have learned to avoid the beginner s mistake of trying to use Collection<Object> as the type of the collection parameter You may or may not have recognized that using Collection<?> isn t going to work either Recall that you cannot just shove objects into a collection of unknown type
30 Generic methods The way to do deal with these problems is to use generic methods Just like class declarations, method declarations can be generic - that is, parameterized by one or more type parameters: static <T> void fromarraytocollection(t[] a, Collection<T> c) { for (T o : a) { c.add(o); // correct
31 Type Inference We can call this method with any kind of collection whose element type is a supertype of the element type of the array Object[] oa = new Object[100]; Collection<Object> co = new ArrayList<Object>(); fromarraytocollection(oa, co);// T inferred to be Object Notice that we don t have to pass an actual type argument to a generic method The compiler infers the type argument for us, based on the types of the actual arguments
32 Type Inference We can call this method with any kind of collection whose element type is a supertype of the element type of the array Object[] oa = new Object[100]; Collection<Object> co = new ArrayList<Object>(); fromarraytocollection(oa, co);// T inferred to be Object String[] sa = new String[100]; Collection<String> cs = new ArrayList<String>(); fromarraytocollection(sa, cs); fromarraytocollection (java.lang.string[] a, Collection<java.lang.String> c Notice that we don t have to pass an actual type argument to a generic method The compiler infers the type argument for us, based on the types of the actual arguments
33 Type Inference We can call this method with any kind of collection whose element type is a supertype of the element type of the array Object[] oa = new Object[100]; Collection<Object> co = new ArrayList<Object>(); fromarraytocollection(oa, co);// T inferred to be Object String[] sa = new String[100]; Collection<String> cs = new ArrayList<String>(); fromarraytocollection(sa, cs);// T inferred to be String fromarraytocollection(sa, co);// Notice that we don t have to pass an actual type argument to a generic method The compiler infers the type argument for us, based on the types of the actual arguments
34 Type Inference We can call this method with any kind of collection whose element type is a supertype of the element type of the array Object[] oa = new Object[100]; Collection<Object> co = new ArrayList<Object>(); fromarraytocollection(oa, co);// T inferred to be Object String[] sa = new String[100]; Collection<String> cs = new ArrayList<String>(); fromarraytocollection(sa, cs);// T inferred to be String fromarraytocollection(sa, co);// T inferred to be Object Integer[] ia = new Integer[100]; Float[] fa = new Float[100]; Number[] na = new Number[100]; Collection<Number> cn = new ArrayList<Number>(); fromarraytocollection(ia, cn);// Notice that we don t have to pass an actual type argument to a generic method The compiler infers the type argument for us, based on the types of the actual arguments
35 Type Inference We can call this method with any kind of collection whose element type is a supertype of the element type of the array Object[] oa = new Object[100]; Collection<Object> co = new ArrayList<Object>(); fromarraytocollection(oa, co);// T inferred to be Object String[] sa = new String[100]; Collection<String> cs = new ArrayList<String>(); fromarraytocollection(sa, cs);// T inferred to be String fromarraytocollection(sa, co);// T inferred to be Object Integer[] ia = new Integer[100]; Float[] fa = new Float[100]; Number[] na = new Number[100]; Collection<Number> cn = new ArrayList<Number>(); fromarraytocollection(ia, cn);// T inferred to be Number fromarraytocollection(fa, cn);// T inferred to be Number fromarraytocollection(na, cn);// T inferred to be Number fromarraytocollection(na, co);// T inferred to be Object fromarraytocollection(oa,cs); Notice that we don t have to pass an actual type argument to a generic method The compiler infers the type argument for us, based on the types of the actual arguments
36 Type Inference We can call this method with any kind of collection whose element type is a supertype of the element type of the array Object[] oa = new Object[100]; Collection<Object> co = new ArrayList<Object>(); fromarraytocollection(oa, co);// T inferred to be Object String[] sa = new String[100]; Collection<String> cs = new ArrayList<String>(); fromarraytocollection(sa, cs);// T inferred to be String fromarraytocollection(sa, co);// T inferred to be Object Integer[] ia = new Integer[100]; Float[] fa = new Float[100]; Number[] na = new Number[100]; Collection<Number> cn = new ArrayList<Number>(); fromarraytocollection(ia, cn);// T inferred to be Number fromarraytocollection(fa, cn);// T inferred to be Number fromarraytocollection(na, cn);// T inferred to be Number fromarraytocollection(na, co);// T inferred to be Object fromarraytocollection(oa,cs);
37 Type Inference We can call this method with any kind of collection whose element type is a supertype of the element type of the array Object[] oa = new Object[100]; Collection<Object> co = new ArrayList<Object>(); fromarraytocollection(oa, co);// T inferred to be Object String[] sa = new String[100]; Collection<String> cs = new ArrayList<String>(); fromarraytocollection(sa, cs);// T inferred to be String fromarraytocollection(sa, co);// T inferred to be Object Integer[] ia = new Integer[100]; Float[] fa = new Float[100]; Number[] na = new Number[100]; Collection<Number> cn = new ArrayList<Number>(); fromarraytocollection(ia, cn);// T inferred to be Number fromarraytocollection(fa, cn);// T inferred to be Number fromarraytocollection(na, cn);// T inferred to be Number fromarraytocollection(na, co);// T inferred to be Object fromarraytocollection(oa,cs);//compile-time error fromarraytocollection(na, cs);//
38 When should I use generic methods, and when should I use wildcard types? interface Collection<E> { public <T> boolean containsall(collection<t> c); public <T extends E> boolean addall(collection<t> c);?? interface Collection<E> { public boolean containsall(collection<?> c); public boolean addall(collection<? extends E> c); The type parameter T is used only once. The return type doesn t depend on the type parameter, nor does any other argument to the method This tells us that the type argument is being used for polymorphism; its only effect is to allow a variety of actual argument types to be used at different invocation sites. If that is the case, one should use wildcards. Wildcards are designed to support flexible subtyping, which is what we re trying to express here Generic methods allow type parameters to be used to express dependencies among the types of one or more arguments to a method and/or its return type If there isn t such a dependency, a generic method should not be used
39 It is possible to use both generic methods and wildcards in tandem class Collections { public static <T> void copy(list<t> dest, List<? extends T> src){... Note the dependency between the types of the two parameters Any object copied from the source list, src, must be assignable to the element type T of the destination list, dst So the element type of src can be any subtype of T - we don t care which The signature of copy expresses the dependency using a type parameter, but uses a wildcard for the element type of the second parameter We could have written the signature for this method another way, without using wildcards at all: class Collections { public static <T, S extends T> void copy(list<t> dest, List<S> src){... This is fine, but while the first type parameter is used both in the type of dst and in the bound of the second type parameter, S, S itself is only used once, in the type of src - nothing else depends on it. This is a sign that we can replace S with a wildcard
40 Implementation Generics are implemented by the Java compiler as a front-end conversion called erasure You can (almost) think of it as a source-to-source translation, whereby the generic version is converted to the non-generic version Basically, erasure gets rid of (or erases) all generic type information. All the type information between angle brackets is thrown out, so, for example, a parameterized type like List<String> is converted into List All remaining uses of type variables are replaced by the upper bound of the type variable (usually Object) And, whenever the resulting code isn t type-correct, a cast to the appropriate type is inserted The full details of erasure are beyond our scope, but the simple description we just gave isn t far from the truth
41 Type Erasure Generics were introduced to the Java language to provide tighter type checks at compile time and to support generic programming. To implement generics, the Java compiler applies type erasure to: Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods. Insert type casts if necessary to preserve type safety. Generate bridge methods to preserve polymorphism in extended generic types. Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead.
42 Erasure and Translation Here, we ve aliased a list of strings and a plain old list: public String loophole(integer x) { List<String> ys = new LinkedList<String>(); List xs = ys; xs.add(x); // compile-time unchecked warning return ys.iterator().next(); We insert an Integer into the list, and attempt to extract a String. This is clearly wrong If we ignore the warning and try to execute this code, it will fail exactly at the point where we try to use the wrong type At run time, this code behaves like: public String loophole(integer x) { List ys = new LinkedList; List xs = ys; xs.add(x); return (String) ys.iterator().next(); // run time error
43 Example (2) public <T> T testgeneric(t x){ List<T> ys = new LinkedList<T>(); System.out.println(x); ys.add(x); return ys.iterator().next(); Integer answer = ts.testgeneric(new Integer(1)); String str = ts.testgeneric( Generic"); public Object testgeneric(object x){ List ys = new LinkedList(); System.out.println(x); ys.add(x); return ys.iterator().next(); Integer answer = (Integer) ts.testgeneric(new Integer(1)); String str = (String) ts.testgeneric( Generic");
44 A Generic Class is Shared by all its Invocations What does the following code fragment print? List <String> l1 = new ArrayList<String>(); List<Integer> l2 = new ArrayList<Integer>(); System.out.println(l1.getClass() == l2.getclass()); You might be tempted to say false, but you d be wrong. It prints true, because all instances of a generic class have the same run-time class, regardless of their actual type parameters. Indeed, what makes a class generic is the fact that it has the same behavior for all of its possible type parameters; the same class can be viewed as having many different types As consequence, the static variables and methods of a class are also shared among all the instances.
45 Casts and InstanceOf Another implication of the fact that a generic class is shared among all its instances, is that it usually makes no sense to ask an instance if it is an instance of a particular invocation of a generic type: Collection cs = new ArrayList<String>(); if (cs instanceof Collection<String>) {... // illegal similarly, Collection<String> cstr = (Collection<String>) cs; // unchecked warning The same is true of type variables: <T> T badcast(t t, Object o) {return (T) o; // unchecked warning Type variables don t exist at run time. This means that they entail no performance overhead in either time nor space, which is nice. Unfortunately, it also means that you can t reliably use them in casts
46 More Fun with Wildcards We ve seen examples where bounded wildcards were useful when reading from a data structure Now consider the inverse, a write-only data structure Flush all elements of the collection and return the last element flushed: public static <T> T writeall(collection<t> coll, Sink<T> snk){ T last; for (T t : coll) { last = t; snk.flush(last); return last;... Sink<Object> s; Collection<String> cs; String str = writeall(cs, s); // illegal call
47 A solution? As written, the call to writeall() is illegal, as no valid type argument can be inferred; neither String nor Object are appropriate types for T, because the Collection element and the Sink element must be of the same type We can fix this by modifying the signature of writeall() as shown below, using a wildcard public static <T> T writeall(collection<? extends T>, Sink<T>){...... String str = writeall(cs, s); // call ok, but wrong return type The call is now legal, but the assignment is erroneous, since the return type inferred is Object because T matches the element type of s, which is Object
48 Lower Bounds The solution is to use a form of bounded wildcard we haven t seen yet: wildcards with a lower bound The syntax <? super T> denotes an unknown type that is a supertype of T It is the dual of the bounded wildcards we ve been using, where we use <? extends T> to denote an unknown type that is a subtype of T public static <T> T writeall(collection<t> coll, Sink<? super T> snk){...... String str = writeall(cs, s); // Yes! Using this syntax, the call is legal, and the inferred type is String, as desired
49 Lower / Upper Bounds = Read / Write permission Upper Bound Marked G<? extends X> A descendant of X Allows to read an instance Lower Bound Marked G<? super Y> An ancestor of Y Allows to write to an instance